package animator.phantom.renderer;

/*
    Copyright Janne Liljeblad

    This file is part of Phantom2D.

    Phantom2D is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Phantom2D is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Phantom2D.  If not, see <http://www.gnu.org/licenses/>.
*/

import animator.phantom.renderer.param.AnimatedImageCoordinates;
import animator.phantom.plugin.PluginUtils;
import giotto2D.core.GeometricFunctions;
import org.w3c.dom.*;
import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.transcoder.*;
import org.apache.batik.dom.util.DOMUtilities;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.util.XMLResourceDescriptor;
import org.w3c.dom.svg.SVGDocument;
import java.awt.Dimension;
import java.awt.geom.*;
import java.util.*;
import java.awt.image.*;
import java.awt.*;


//--- BATIK WONT RENDER NESTED G TAGS CORRECTLY TO BufferedImages
//--- FEATURE DISABLED
public class SVGDocumentTransformer
{
	//--- transform key words
	private static final String TRANSFORM = "transform";
	private static final String TRANSLATE = "translate";
	private static final String SCALE = "scale";
	private static final String ROTATE = "rotate";
	private static final String SKEW_X = "skewX";
	private static final String SKEW_Y = "skewY";
	private static final String GROUP = "g";
	private static final String SVG_GROUP = "svg:g";
	private static final String SWITCH = "switch";

	private static final String[] DRAW_ELEMS = { 	"circle","clipPath"," ellipse","image", "line","linearGradient",
							"mask","path","pattern","polygon","polyline","radialGradient",
							"rect","tspan" };

	private static final String SVG_NS = "http://www.w3.org/2000/svg";

	private Document doc;
	private Element svgRoot;
	private SVGAffineValue frameTransform;
	private float wCor = 1.0f;
	private float hCor = 1.0f;
	private boolean hasViewBox = false;

	public SVGDocumentTransformer( Document oldDoc )
	{
		DOMImplementation domImpl = SVGDOMImplementation.getDOMImplementation();

		this.doc = domImpl.createDocument( SVG_NS, "svg", null );
		this.doc.replaceChild( 	this.doc.importNode( oldDoc.getDocumentElement(), true ),
					this.doc.getDocumentElement() );

		svgRoot = doc.getDocumentElement();
	}

	public void setTransform(  float frameTime,
				   AnimatedImageCoordinates coords,
					Dimension screenSize,
					boolean flipH,
					boolean flipV )
	{
		//--. viewBox attr correction
		/*
		String oldWidth = svgRoot.getAttribute( "width" );
		String oldHeight = svgRoot.getAttribute( "height" );		
		if( svgRoot.hasAttribute( "viewBox" ) )
		{
			try
			{
				String viewBoxVal = svgRoot.getAttribute( "viewBox" );
				Vector<Double> values = getValues( viewBoxVal,  " " );
				System.out.println( values.elementAt( 2 ).floatValue() );
				System.out.println( values.elementAt( 3 ).floatValue() );
				wCor = Float.parseFloat( oldWidth ) / values.elementAt( 2 ).floatValue(); 
				hCor =  Float.parseFloat( oldHeight ) / values.elementAt( 3 ).floatValue();

			}
			catch( Exception e ){}
		}
		*/
		svgRoot.removeAttribute( "viewBox" );

		//--- Set image size to screen size.
		Integer sw = new Integer( screenSize.width );
		Integer sh = new Integer( screenSize.height );
		svgRoot.setAttribute( "width" , sw.toString() );
		svgRoot.setAttribute( "height", sh.toString() );

		//--- Get value that represents transformation in time for AnimatedImageCoordinates object.
		this.frameTransform = getAffineValueForFrame( frameTime, coords, screenSize );
	}

	public void transform()
	{
		transformGroupElements();
		transformDrawElements();
	}

	public Document getTransformedDocument()
	{
		return doc;
	}

	private void transformGroupElements()
	{
		NodeList liveNodes = getLiveNodes( GROUP );
		Vector<Element> groupList = getTransformableElements( liveNodes );
		transformElements( groupList );
	}

	private void transformDrawElements()
	{
		Vector<Element> drawElements = getDrawElements();
		transformElements( drawElements );
	}

	private NodeList getLiveNodes( String tag )
	{
		NodeList liveNodes = doc.getElementsByTagName( tag );
		// If none were found, try with "svg:" + tag
		if( liveNodes.getLength() == 0 )
		{
 			tag = "svg:" + tag;
			liveNodes = doc.getElementsByTagName( tag );
		}
		return liveNodes;
	}

	private Vector<Element> getTransformableElements( NodeList liveNodes )
	{
		Vector<Element> elements = new Vector<Element>();
		for( int i = 0; i < liveNodes.getLength(); i++ )
		{
			Node node = liveNodes.item( i );
			if( !hasGroupParent( node ) )
				elements.add( (Element) node );
		}
		return elements;
	}

	private void transformElements( Vector<Element> elements )
	{
		for( int i = 0; i < elements.size(); i++ )
		{
			Element e = elements.elementAt( i );

			Node parent = e.getParentNode();
			Element transGroup = getTransformingGroupElement( frameTransform );
			parent.replaceChild( transGroup, e);
			transGroup.appendChild( e );
		}
	}

	private Vector<Element> getDrawElements()
	{
		Vector<Element> drawElements = new Vector<Element>();

		for( int i = 0; i < DRAW_ELEMS.length; i++ )
		{
			NodeList liveNodes = getLiveNodes( DRAW_ELEMS[ i ] );
			for( int j = 0; j < liveNodes.getLength(); j++ )
			{
				Node node = liveNodes.item( j );
				if( !hasGroupParent( node ) )
					drawElements.add( (Element) node );
			}
		}
		return drawElements;
	}

	private boolean hasGroupParent( Node node )
	{
		Element parent = (Element) node.getParentNode();
		while( parent != svgRoot )
		{
			if( parent.getNodeName().equals( GROUP ) ||
			    parent.getNodeName().equals( SVG_GROUP ) )
				return true;
			parent = (Element) parent.getParentNode();
		}
		return false;
	}

	private SVGAffineValue getAffineValueForFrame( 	float frameTime, 
							AnimatedImageCoordinates coords, 
							Dimension screenSize )
	{
		float x = coords.x.getValue( frameTime );
		float y = coords.y.getValue( frameTime );
		float scaleX = coords.xScale.getValue( frameTime ) / AnimatedImageCoordinates.xScaleDefault;
		float scaleY =  coords.yScale.getValue( frameTime ) / AnimatedImageCoordinates.yScaleDefault;
		float anchorX = coords.xAnchor.getValue( frameTime );
		float anchorY =  coords.yAnchor.getValue( frameTime );
		float rotation = coords.rotation.getValue( frameTime );

		//--- Correct for possibly removed viewBox
		//scaleX = scaleX * wCor;
		//scaleY = scaleY * hCor;

		//--- Calculate transform.
		//--- Get scaled anchor offsets.
		anchorX = scaleX * anchorX;
		anchorY = scaleY * anchorY;

		//--- Get offset to topleft corner of scaled and rotated image from anchor point.
		Point2D.Float topLeftOffset = new Point2D.Float( -anchorX, -anchorY );
		topLeftOffset = GeometricFunctions.rotatePointAroundOrigo( rotation, topLeftOffset );

		//--- Get translation to topleft point = scaled and rotated topleft point  + x and y translation.
		float tx = x + topLeftOffset.x;
		float ty = y + topLeftOffset.y;

		//--- Build return value
		SVGAffineValue av = new SVGAffineValue();
		av.x = (double) tx;
		av.y = (double) ty;
		av.scaleX = (double) scaleX;
		av.scaleY = (double) scaleY;
		av.rotation = (double) rotation;

		return av;
	}

	private Element getTransformingGroupElement( SVGAffineValue frameTransform )
	{
		Element transGroup = doc.createElementNS( SVG_NS,GROUP );
		transGroup.setAttribute( TRANSFORM, getTransformString( frameTransform ) );
		return transGroup;
	}



	private SVGAffineValue getAffineValue( String s )
	{
		SVGAffineValue av = new SVGAffineValue();
		StringTokenizer st = new StringTokenizer( s, ")" );
		while (st.hasMoreTokens()) 
		{
			String token = st.nextToken();
			System.out.println( token );
			int opEnd  = token.indexOf( "(" ); 
			String op = token.substring( 0, opEnd );
			String valStr = token.substring( opEnd + 1, token.length() );
			Vector<Double> values = getValues( valStr,  ", " );
			if( op.equals( TRANSLATE ) )
			{
				av.x = values.elementAt( 0 );
				if( values.size() == 2 )
					av.y = values.elementAt( 1 );
			}
			else if( op.equals( ROTATE ) )
			{
				av.rotation = values.elementAt( 0 );
			}
			else if( op.equals( SCALE ) )
			{
				av.x = values.elementAt( 0 );
				if( values.size() == 2 )
					av.y = values.elementAt( 1 );
				else
					av.y = values.elementAt( 0 );
			}
			else if( op.equals( SKEW_X ) )
			{
				av.skewX = values.elementAt( 0 );
			}
			else if( op.equals( SKEW_Y ) )
			{
				av.skewY = values.elementAt( 0 );
			}
		}

		return av;
	}

	private String getTransformString( SVGAffineValue av )
	{
		//--- Build transform string.
		StringBuffer tform = new StringBuffer();
		//--- translate
		tform.append( TRANSLATE + "(" );
		tform.append( (new Double( av.x )).toString() );
		tform.append( " " );
		tform.append( (new Double( av.y )).toString() );
		tform.append( ")" );
		tform.append( " " );
		//--- rotate
		tform.append( ROTATE +"(" );
		tform.append( (new Double( av.rotation )).toString() );
		tform.append( ")" );
		tform.append( " " );
		//--- scale
		tform.append( SCALE + "(" );
		tform.append( (new Double( av.scaleX )).toString() );
		tform.append( " " );
		tform.append( (new Double( av.scaleY )).toString() );
		tform.append( ")" );
		tform.append( " " );

		//--- skew
		tform.append( SKEW_X + "(" );
		tform.append( (new Double( av.skewX )).toString() );
		tform.append( ")" );
		tform.append( " " );
		tform.append( SKEW_Y + "(" );
		tform.append( (new Double( av.skewY )).toString() );
		tform.append( ")" );

		return tform.toString();
	}

	private Vector<Double> getValues( String v, String tokenizer )
	{
		//--- Tokenize and parse values
		Vector<Double> values = new Vector<Double>();
		StringTokenizer st = new StringTokenizer( v, tokenizer );
		while( st.hasMoreTokens() ) 
		{
			String token = st.nextToken();
			values.add( Double.parseDouble( token ) );
		}
		return values;
	}

}//end class



